﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ClassProxitator
{
    public class StreamProxy : IProxyClass
    {
        /// <summary>
        /// tcp port -> Thread of the server
        /// </summary>
        static readonly ConcurrentDictionary<int, TcpListener> Servers = new ConcurrentDictionary<int, TcpListener>();
        public static List<StreamProxy> ListenAt(int port, Func<IConnection> connectionCreator)
        {
            if (Servers.ContainsKey(port)) throw new IOException("already listening on port " + port);
            var proxies = new List<StreamProxy>();
            var server = new TcpListener(IPAddress.Any, port);
            if (!Servers.TryAdd(port, server))
            {
                return proxies;
            }
            server.Start();
            ListenInternal(server, proxies, connectionCreator).ContinueWith(t =>
            {
                StopListening(port);
                server.Stop();
            });
            return proxies;
        }

        private static async Task ListenInternal(TcpListener server, List<StreamProxy> proxies, Func<IConnection> connectionCreator)
        {
            while (true)
            {
                var client = await server.AcceptTcpClientAsync();
                var str = client.GetStream();
                var proxy = new StreamProxy(str, connectionCreator());
                proxies.Add(proxy);
            }
        }

        public static void StopListening(int port)
        {
            TcpListener t;
            if (Servers.TryRemove(port, out t))
            {
                t.Stop();
            }
        }

        public IConnection Connection
        {
            get
            {
                return _strconn.Connections.First();
            }
            set
            {
                _strconn.Connections.Clear();
                _strconn.Connections.Add(value);
            }
        }

        StreamConnection _strconn;

        public StreamProxy(Stream read, Stream write, IConnection connection)
        {
            this._strconn = new StreamConnection(read, write);
            _strconn.Connections.Add(connection);
        }

        public StreamProxy(Stream readWrite, IConnection connection)
        {
            _strconn = new StreamConnection(readWrite);
            _strconn.Connections.Add(connection);
        }
    }
}
